package edu.cmu.arch.pipesandfilters.utils;

import java.util.Random;

/**
 * A somewhat archaic but useful utility to convert fixed number of bytes
 * to Java primitives and vice versa.
 * @author Kyle Benedict (jbenedic@andrew.cmu.edu)
 */
public class BitwiseManipulation {

	/**
	 * Convert four bytes into a 32 bit integer
	 * @param byteArr
	 * @return int
	 */
	public static final int getIntegerFromBytes(byte[] byteArr) throws IllegalArgumentException {
		if (byteArr == null || byteArr.length != 4) {
			throw new IllegalArgumentException("A 32 bit integer must be converted from exactly four bytes");
		}
		int value = 0;
		for (int i = 0; i < byteArr.length; i++) {			
			value = value | (byteArr[i] & 0xFF);	// Append the byte
			if (i != byteArr.length - 1) {			// If this is not the last byte, then slide the
				value = value << 8;					// previously appended byte to the left by one byte
			}										// to make room for the next byte we append to the ID
		}
		return value;
	}
	
	/**
	 * Convert eight bytes into a 64 bit long integer
	 * @param byteArr
	 * @return long
	 */
	public static final long getLongFromBytes(byte[] byteArr) throws IllegalArgumentException {
		if (byteArr == null || byteArr.length != 8) {
			throw new IllegalArgumentException("A 64 bit long integer must be converted from exactly eight bytes");
		}
		long value = 0;
		for (int i = 0; i < byteArr.length; i++) {			
			value = value | (byteArr[i] & 0xFF);	// Append the byte
			if (i != byteArr.length - 1) {			// If this is not the last byte, then slide the
				value = value << 8;					// previously appended byte to the left by one byte
			}										// to make room for the next byte we append to the ID
		}
		return value;
	}	
	
	/**
	 * Convert eight bytes into a 64 bit double precision float
	 * @param byteArr
	 * @return double
	 */
	public static final double getDoubleFromBytes(byte[] byteArr) throws IllegalArgumentException {
		if (byteArr == null || byteArr.length != 8) {
			throw new IllegalArgumentException("A 64 bit double precision float must be converted from exactly eight bytes");
		}		
		// Convert the bytes to long, and then from long to double using Double.longBitsToDouble		
		double value = Double.longBitsToDouble(getLongFromBytes(byteArr));
		return value;
	}	

	/**
	 * Convert 32 bit integer into four bytes
	 * @param int
	 * @return byte[]
	 */
	public static final byte[] getBytesFromInteger(int value) {
		byte[] byteArr = new byte[4];
		for (int i = byteArr.length - 1; i > -1; i--) {
			if (i < byteArr.length - 1) {
				value = value >> 8;
			}
			byteArr[i] = (byte) (value & 0xff);
		}
		return byteArr;
	}		
	
	/**
	 * Convert 64 bit long integer into eight bytes
	 * @param long
	 * @return byte[]
	 */
	public static final byte[] getBytesFromLong(long value) {
		byte[] byteArr = new byte[8];
		for (int i = byteArr.length - 1; i > -1; i--) {
			if (i < byteArr.length - 1) {
				value = value >> 8;
			}
			byteArr[i] = (byte) (value & 0xff);
		}
		return byteArr;
	}		
		
	/**
	 * Convert 64 bit double precision float into eight bytes
	 * @param double
	 * @return byte[]
	 */
	public static final byte[] getBytesFromDouble(double value) {
		long longValue = Double.doubleToLongBits(value);
		// Convert the long to bytes
		return getBytesFromLong(longValue);
	}

	////////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////
	// NOTE:  To validate the conversion from bytes to primitives and vice versa
	// I have developed a few simple validate methods.  You can invoke them
	// through the main method in order to confirm that the conversion to 
	// and from creates a consistent result.	
	public static void main(String args[]) {
		System.out.println("Begin validation tests for converters");
		long seed = 12345;
		long iterations = 500;
		System.out.println("Integer conversions are " + (validateIntegerConversion(seed, iterations) == true ? "valid" : "invalid"));
		System.out.println("Long conversions are " + (validateLongConversion(seed, iterations) == true ? "valid" : "invalid"));
		System.out.println("Double conversions are " + (validateDoubleConversion(seed, iterations) == true ? "valid" : "invalid"));
		System.out.println("End validation tests for converters");
	}	
	
	private static boolean validateIntegerConversion(long seed, long iterations) {		
		int successfulConversionCount = 0;
		Random random = new Random(seed);
		for (int i = 0; i < iterations; i++) {
			int i0 = random.nextInt();
			byte[] byteArr = getBytesFromInteger(i0);
			int i1 = getIntegerFromBytes(byteArr);
			if (i0 == i1) {
				successfulConversionCount++;
			}
		}
		return successfulConversionCount == iterations;
	}	
	
	private static boolean validateLongConversion(long seed, long iterations) {		
		int successfulConversionCount = 0;
		Random random = new Random(seed);
		for (int i = 0; i < iterations; i++) {
			long l0 = random.nextLong();
			byte[] byteArr = getBytesFromLong(l0);
			long l1 = getLongFromBytes(byteArr);
			if (l0 == l1) {
				successfulConversionCount++;
			}
		}
		return successfulConversionCount == iterations;
	}
	
	private static boolean validateDoubleConversion(long seed, long iterations) {		
		int successfulConversionCount = 0;
		Random random = new Random(seed);
		for (int i = 0; i < iterations; i++) {
			double d0 = random.nextDouble();
			byte[] byteArr = getBytesFromDouble(d0);
			double d1 = getDoubleFromBytes(byteArr);
			if (d0 == d1) {
				successfulConversionCount++;
			}

		}
		return successfulConversionCount == iterations;
	}
	
}
